#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.Text;
using System.Linq;
using System.Linq.Expressions;
using System.Globalization;

namespace Yarr
{
	/// <summary>
	/// A closure is a function defined with a captured environment
	/// </summary>
	public class Closure
	{
        #region private members
		// The captured environment
		private Environment environment;

		// The body is a Yarr function body ...
		private Cons body;

        // ... or a delegate for a .NET method
        Func<Environment, Object> internalMethod;

        // Additional supported delegate types
        private Delegate funcDelegate;
        private Delegate predicateDelegate;
        private Delegate actionDelegate;
        private Expression lambda;

        // The return type of the closure
        private Type returnType;

        // The list of named arguments
		private Cons argumentNames;

        // Track the recursion depth, so that we can try 
        // to spot stack-overflows before they happen
        private int recursionDepth;
        #endregion


        #region Properties
        /// <summary>
        /// Get the body of the closure
        /// </summary>
        public Cons Body { get { return body; } }

        /// <summary>
        /// Get an Action<...> that wraps this closure
        /// </summary>
        public Delegate Action
        {
            get
            {
                if (actionDelegate == null)
                    actionDelegate = FunctionBinding.BindActionDelegate(this);

                return actionDelegate;
            }
        }

        /// <summary>
        /// Get an Expression<Func<...>> for the closure
        /// </summary>
        public Expression Expression
        {
            get
            {
                if (lambda == null)
                    lambda = FunctionBinding.BindExpression(this);

                return lambda;
            }
        }

        /// <summary>
        /// Get a Func<...> that wraps this closure
        /// </summary>
        public Delegate Func 
        { 
            get 
            { 
                if (funcDelegate == null)
                    funcDelegate = (Delegate)FunctionBinding.BindFuncDelegate(this); 

                return funcDelegate; 
            } 
        }

        /// <summary>
        /// Get a Predicate<...> that wraps this closure
        /// </summary>
        public Delegate Predicate
        {
            get
            {
                if (predicateDelegate == null)
                    predicateDelegate = FunctionBinding.BindPredicate(this);

                return predicateDelegate;
            }
        }

        /// <summary>
        /// Get the argument list of the closure
        /// </summary>
        public Cons Arguments { get { return argumentNames; } }

        /// <summary>
        /// The return type of the closure
        /// </summary>
        public Type ReturnType { get { return returnType; } }
         #endregion


        #region Constructors
        /// <summary>
		/// Defines a closure (function) in terms of its arguments, body and
		/// the environment in which it is defined
        /// </summary>
		/// <param name="argumentNames">A list of argument names</param>
		/// <param name="body"> A Yarr program which is the body of the function</param>
		/// <param name="environment">The environment to enclose</param>
        public Closure(Cons argumentNames, Cons body, Environment environment)
            :this(argumentNames, body, typeof(Object), environment) { }

		/// <summary>
		/// Defines a closure (function) in terms of its arguments, body and
		/// the environment in which it is defined
		/// </summary>
		/// <param name="argumentNames">A list of argument names</param>
		/// <param name="body"> A Yarr program which is the body of the function</param>
        /// <param name="returnType">The return type of this closure</param>
		/// <param name="environment">The environment to enclose</param>
		public Closure(Cons argumentNames, Cons body, Type returnType, Environment environment)
		{
			this.environment = environment;
			this.body = body;
            this.internalMethod = null;
            this.returnType = returnType;
			this.argumentNames = argumentNames;
		}

        /// <summary>
        /// Defines a closure (function) in terms of its arguments, delegatge and
        /// the environment in which it is defined
        /// </summary>
        /// <param name="argumentNames">A list of argument names</param>
        /// <param name="func"> A delegate that implements the function</param>
        /// <param name="environment">The environment to enclose</param>
        public Closure(Cons argumentNames, Func<Environment, object> func, Environment environment)
        {
            this.environment = environment;
            this.body = null;
            this.internalMethod = func;
            this.returnType = typeof(Object);
            this.argumentNames = argumentNames;
        }
    #endregion


        #region Methods
        /// <summary>
        /// Return an array of argument types for the closure
        /// </summary>
        /// <returns></returns>
        internal Type[] GetArgumentTypes(bool includeReturnType)
        {
            Type[] argTypes = new Type[(includeReturnType ? 1 : 0) +
                                       (argumentNames == null ? 0 : argumentNames.Length)];
            int ix = 0;
            Cons args = argumentNames;

            while (args != null)
            {
                Object arg = args.Car();
                if (arg is Symbol)
                {
                    switch (((Symbol)arg).Name)
                    {
                        case "&rest":
                        case "&optional":
                        case "&key":
                        case "&body":
                            return null;

                        default:
                            argTypes[ix] = typeof(Object);
                            break;
                    }
                }
                else if (arg is Cons)
                {
                    argTypes[ix] = TypeCache.FindType(((Cons)arg).Cadr().ToString());
                    if (argTypes[ix] == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture,
                                    "GetArgumentTypes: Type {0} not found", ((Cons)arg).Cadr().ToString()));
                }

                ++ix;
                args = (Cons)args.Cdr();
            }

            if (includeReturnType)
                argTypes[ix] = this.returnType;

            return argTypes;
        }

		/// <summary>
		/// Invokes the closure with no arguments
		/// </summary>
		/// <returns>The result of invoking the delegate or Yarr function</returns>
		internal Object Invoke() 
		{
			return Invoke (null);
		}

		/// <summary>
		/// Invokes the closure with arguments bound to the values specified in the 
		/// argument list
		/// </summary>
		/// <param name="arguments">A list of arguments</param>
        /// <returns>The result of invoking the delegate or Yarr function</returns>
        internal Object Invoke(Cons arguments) 
		{
            if (environment.Contains(Symbol.MAX_RECUR_DEPTH)  &&
                this.recursionDepth >= (int)environment.GetValue(Symbol.MAX_RECUR_DEPTH))
                    throw new YarrException("Invoke: maximum recursion depth exceeded");

            try
            {
                ++this.recursionDepth;

                // Create a new lexical environment
                Environment localEnvironment = Environment.MakeEnvironment(environment);

                localEnvironment.AssignLocal(Symbol.SELF, this);

                if (body != null  ||  argumentNames != null)
                    Primitives.ProcessArguments(argumentNames, arguments, localEnvironment);

                Object result = (body != null) ? Runtime.Do(body, localEnvironment) : 
                                                 internalMethod(localEnvironment);

                if (!Primitives.IsTypeOf(returnType, result))
                {
                    if (!Conversions.TryImplicitConversion(result, returnType, out result))
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                            "Invoke: Closure with return type of {0} attempting to return {1}",
                                            returnType, result.GetType()));
                }

                return result;
            }
            finally
            {
                --this.recursionDepth;
            }
		}

		/// <summary>
		/// Returns a string that describes this closure
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
            return String.Format(CultureInfo.CurrentCulture, "Closure (fn {0}{1}{2})", 
				Printer.WriteToString(argumentNames), 
                returnType == typeof(Object) ? "" :
                                String.Format(CultureInfo.CurrentCulture, 
                                        " :returns {0} ", Printer.WriteToString(returnType)),
				(body == null) ? internalMethod.ToString() : Printer.WriteToString(body, false));
		}
        #endregion
	}
}
